package main

import (
	"database/sql"
	"fmt"
	"gitee.com/flycash/xiaohongshu-demo/internal/repository"
	"gitee.com/flycash/xiaohongshu-demo/internal/repository/cache"
	"gitee.com/flycash/xiaohongshu-demo/internal/repository/dao"
	"gitee.com/flycash/xiaohongshu-demo/internal/service"
	"gitee.com/flycash/xiaohongshu-demo/internal/web"
	"gitee.com/flycash/xiaohongshu-demo/internal/web/middleware"
	iplimiter "github.com/Salvatore-Giordano/gin-redis-ip-limiter"
	"github.com/gin-gonic/gin"
	"github.com/go-redis/redis"
	"github.com/spf13/viper"
	"go.uber.org/zap"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	gormLogger "gorm.io/gorm/logger"
	"log"
	"net/http"
	"os"
	"time"
)

func main() {
	// 初始化配置，按照道理来说，最好是命令行传入配置文件的位置，这里我们就比较粗糙了
	initCfg()

	// 初始化日志
	zapLogger, err := zap.NewDevelopment()
	if err != nil {
		panic(err)
	}
	zap.ReplaceGlobals(zapLogger)

	// 测试开发用的，暂时放这里。确保 docker 的 DB 已经启动成功了
	waitForDB()
	log.Println("DB 已经启动")

	// 初始化 DB
	dbB, dbC := initDB()
	redisClient := redis.NewClient(&redis.Options{
		Addr:     viper.GetString("redis"),
		Password: "",
		DB:       1,
		// 相当于 200 每分钟
	})

	// 测试开发用的，暂时放这里。确保 docker 的 redis 已经启动成功了
	for _, er := redisClient.Ping().Result(); er != nil; {
		log.Println("等待 Redis 启动", er.Error())
		time.Sleep(time.Second)
	}

	//log.Println("Redis 已经启动")

	// 开始组装各种服务，本质上是一个依赖注入的过程
	// 制作库一般也不需要缓存。
	artCache := cache.NewArticleRedisCache(redisClient)
	articleService := service.NewService(repository.NewArticleRepo(dao.NewArticleDAO(dbB), nil),
		repository.NewArticleRepo(dao.NewArticleDAO(dbC), artCache))
	articleCtrl := web.NewArticleController(articleService)

	server := gin.Default()
	server.Use(
		middleware.NewLogMiddlewareBuilder(zapLogger).LogInput().Build(),
		iplimiter.NewRateLimiterMiddleware(redisClient, "general", 200, 60*time.Second),
	)
	server.LoadHTMLGlob("templates/*")
	articleCtrl.RegisterRoutes(server)

	go func() {
		http.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
			writer.WriteHeader(200)
			writer.Write([]byte("hello, world"))
		})
		http.ListenAndServe(":8081", nil)
	}()

	if err := server.Run(":8080"); err != nil {
		log.Fatal(err)
	}
}

func waitForDB() {
	db, err := sql.Open("mysql", viper.GetString("mysql_b"))
	if err != nil {
		panic(err)
	}
	defer func() {
		_ = db.Close()
	}()
	for db.Ping() != nil {
		time.Sleep(time.Second)
	}
}

func initCfg() {
	viper.SetConfigName("config")     // name of config file (without extension)
	viper.SetConfigType("yaml")       // REQUIRED if the config file does not have the extension in the name
	viper.AddConfigPath("/etc/demo/") // path to look for the config file in
	viper.AddConfigPath(".")          // optionally look for config in the working directory
	err := viper.ReadInConfig()
	if err != nil { // Handle errors reading the config file
		panic(fmt.Errorf("解析配置文件失败 %w", err))
	}
}

func initDB() (*gorm.DB, *gorm.DB) {
	logger := gormLogger.New(log.New(os.Stdout, "B端", log.LstdFlags),
		gormLogger.Config{
			SlowThreshold:             time.Millisecond * 100,
			IgnoreRecordNotFoundError: true,
			ParameterizedQueries:      true,
			LogLevel:                  gormLogger.Error,
		})
	db1, er := gorm.Open(mysql.Open(viper.GetString("mysql_b")),
		// 测试开发的时候，禁用了自动 ping，后面我们手动 ping
		&gorm.Config{Logger: logger})
	if er != nil {
		panic(er)
	}

	// 初始化表结构，这部分挪过去 dao 里面，或者直接走线下流程创建好会更加好
	if er = db1.AutoMigrate(&dao.Article{}); er != nil {
		panic(er)
	}

	db2, er := gorm.Open(mysql.Open(viper.GetString("mysql_c")))
	if er != nil {
		panic(er)
	}

	er = db2.Callback().Query().Before("*").Register("slow_query_start", func(db *gorm.DB) {
		now := time.Now()
		db.Set("start_time", now)
	})
	if er != nil {
		panic(er)
	}

	er = db2.Callback().Query().After("*").Register("slow_query_end", func(db *gorm.DB) {
		now := time.Now()
		start, ok := db.Get("start_time")
		if ok {
			duration := now.Sub(start.(time.Time))
			// 设置成 0 ms 仅仅是为了演示的时候能够输出 SQL。
			if duration > 0 {
				zap.L().Debug("慢查询", zap.String("sql", db.Statement.SQL.String()))
			}
		}
	})

	if er != nil {
		panic(er)
	}

	// 初始化表结构
	if er = db2.AutoMigrate(&Article{}); er != nil {
		panic(er)
	}

	return db1, db2
}

type Article struct {
	ID      uint64 `gorm:"primaryKey,autoIncrement"`
	Title   string `form:"title"`
	Content string `form:"content"`
	// 作者 ID
	Author uint64 `gorm:"index,not null"`
	// 创建时间，毫秒作为单位
	Ctime int64
	// 更新时间，毫秒作为单位
	Utime int64
}
